/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.servlet.book.epub;

import offset.nodes.server.servlet.book.html.NoOpAttributesFilter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import offset.nodes.server.servlet.book.AbstractBook;
import offset.nodes.server.servlet.book.AttributesFilter;
import offset.nodes.server.servlet.book.Book;
import offset.nodes.server.servlet.book.BookInfo;
import offset.nodes.server.servlet.book.NameBuilder;
import org.xml.sax.SAXException;

/**
 *
 * @author Walter L�tgenau
 */
public class EpubBook extends AbstractBook implements Book {

    File file;
    Toc toc;
    Opf opf;
    ZipOutputStream zip;
    ZipEntries zipEntries = new ZipEntries();
    static final String PAGE_PATH = "content/";
    public static final String RESOURCE_PATH = "resource/";
    static final String OPF_PATH = "content.opf";
    static final String TOC_PATH = "toc.ncx";
    static final String MIMETYPE_PATH = "mimetype";
    static final String MIMETYPE_CONTENT = "application/epub+zip";
    static final String CONTAINER_PATH = "META-INF/container.xml";
    static final String CONTAINER_CONTENT =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + "<container"
            + " xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\""
            + " version=\"1.0\">"
            + "<rootfiles>"
            + "<rootfile"
            + " full-path=\"content.opf\" "
            + " media-type=\"application/oebps-package+xml\"/>"
            + "</rootfiles>"
            + "</container>";
    Locale locale = null;
    BookInfo info = null;
    XHTMLVersionPostProcessor postProcessor = new XHTMLVersionPostProcessor();
    NameBuilder nameBuilder = new EpubNameBuilder();
    AttributesFilter attributesFilter = new NodesAttributesFilter();

    class ZipEntries {
        HashSet<String> entries = new HashSet<String>();

        public boolean add(ZipEntry entry, byte[] content) throws IOException {
            boolean add = true;
            if (!add || entries.contains(entry.getName()))
                return false;

            zip.putNextEntry(entry);
            zip.write(content);

            entries.add(entry.getName());

            return true;
        }
    }

    /**
     * Represents a epub book.
     *
     * Create a ZipOutputStream to a temporary file.
     * Create the opf and the toc.
     * 
     * @throws IOException
     */
    public EpubBook() throws IOException {
        file = File.createTempFile("epub", null);
        zip = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
        toc = new Toc();
        opf = new Opf();
        zipEntries = new ZipEntries();

        init();
    }

    @Override
    public void setInfo(BookInfo info) {
        this.info = info;
        opf.setBookInfo(info);
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
        opf.setLocale(locale);
    }

    protected void addFile(String path, String content) throws IOException {
        ZipEntry entry = new ZipEntry(path);
        zipEntries.add(entry, content.getBytes());
    }

    protected void init() throws IOException {
        // write the default files with the fixed content
        addFile(MIMETYPE_PATH, MIMETYPE_CONTENT);
        addFile(CONTAINER_PATH, CONTAINER_CONTENT);
    }

    @Override
    public boolean addPage(String repositoryReference, String bookReference, String name, String content, int tocLevel) throws IOException {
        content = postProcessor.postProcessPage(content);
        
        boolean addPage = super.addPage(repositoryReference, bookReference, name, content, tocLevel);
        if (!addPage) {
            if (containsPage(repositoryReference, bookReference) && toc.isInitialized()) 
                toc.add(name, PAGE_PATH + bookReference, tocLevel); // already present somewhere else, add only to toc
            
            return false;
        }

        bookReference = PAGE_PATH + bookReference;
        
        ZipEntry entry = new ZipEntry(bookReference);
        if ( !zipEntries.add(entry, content.getBytes()))
            return false;

        if (!opf.isInitialized()) {
            opf.setTitle(name);
            opf.setId(repositoryReference);
        }

        if (!toc.isInitialized()) {
            toc.setTitle(name);
            toc.setId(bookReference);
        }

        if (opf.addPage(repositoryReference, bookReference, content))
            toc.add(name, bookReference, tocLevel);

        return true;
    }

    @Override
    public void addResource(String repositoryReference, String bookReference, String contentType, byte[] content) throws IOException {
        ZipEntry entry = new ZipEntry(PAGE_PATH + bookReference);
        if (!zipEntries.add(entry, content))
            return;

        opf.addResource(repositoryReference, PAGE_PATH + bookReference, contentType);
    }

 @Override
    public void addResource(String repositoryReference, String bookReference, String contentType, String content) throws IOException {
        addResource(repositoryReference, bookReference, contentType, content.getBytes());
    }


    @Override
    public void write(OutputStream output) throws IOException {
        try {
            zip.flush();
            
            // add opf file
            ZipEntry entry = new ZipEntry(OPF_PATH);
            zip.putNextEntry(entry);
            opf.write(zip);

            // add toc file
            entry = new ZipEntry(TOC_PATH);
            zip.putNextEntry(entry);
            toc.write(zip);
            zip.close();
        } catch (SAXException ex) {
            Logger.getLogger(EpubBook.class.getName()).log(Level.SEVERE, null, ex);
            throw new IOException(ex.getMessage());
        }

        // copy file to output stream
        InputStream input = null;
        try {
            input = new BufferedInputStream(new FileInputStream(file));
            byte[] buf = new byte[4096];

            int count = 0;
            while ((count = input.read(buf)) > 0) {
                output.write(buf, 0, count);
            }
        } finally {
            input.close();
            file.delete();
        }
    }

    public NameBuilder getNameBuilder() {
        return nameBuilder;
    }

    @Override
    public boolean isScriptingEnabled() {
        return false;
    }

    @Override
    public AttributesFilter getAttributesFilter() {
        return attributesFilter;
    }
}
